/*
 * This file or a portion of this file is licensed under the terms of
 * the Globus Toolkit Public License, found in file GTPL, or at
 * http://www.globus.org/toolkit/download/license.html. This notice must
 * appear in redistributions of this file, with or without modification.
 *
 * Redistributions of this Software, with or without modification, must
 * reproduce the GTPL in: (1) the Software, or (2) the Documentation or
 * some other similar material which is provided with the Software (if
 * any).
 *
 * Copyright 1999-2004 University of Chicago and The University of
 * Southern California. All rights reserved.
 */

package org.griphyn.vdl.toolkit;

import java.io.*;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import java.sql.SQLException;
import edu.isi.pegasus.common.util.Version;
import edu.isi.pegasus.common.util.Currently;
import edu.isi.pegasus.common.util.Separator;
import org.griphyn.vdl.parser.VDLxParser;
import org.griphyn.vdl.classes.*;
import org.griphyn.vdl.dbschema.*;
import org.griphyn.vdl.util.Logging;
import org.griphyn.vdl.util.ChimeraProperties;
import org.griphyn.vdl.directive.*;
import gnu.getopt.*;

/**
 * This class searches definitions that match the namespace, name,
 * version triple, then prints the search results in one of the formats:
 * vdlx, vdlt, or name. Alternatively, it permits to search from a list
 * of inputs, either DVs or LFNs. 
 *
 * @author Jens-S. Vöckler
 * @author Yong Zhao
 * @version $Revision: 2079 $
 *
 * @see org.griphyn.vdl.parser.VDLxParser
 */

public class SearchVDC extends Toolkit 
{
  /**
   * Constructor
   */
  public SearchVDC(String appName)
  {
    super(appName);
  }

  /**
   * Prints the usage string onto stdout.
   */
  public void showUsage()
  {
    String linefeed = System.getProperty( "line.separator", "\r\n" );

    System.out.println(
"$Id: SearchVDC.java 2079 2010-04-19 23:31:11Z vahi $" + linefeed +
"VDS version " + Version.instance().toString() + linefeed );

    System.out.println(
"Usage: " + this.m_application + " [general] [-t tr|dv] [[-n ns] [-i id] [-v vs] | -D dv] [-L lod]" + linefeed +
"   or: " + this.m_application + " [general] [-t i|o|io] [-f lfn | -F lof]" );

    System.out.println( linefeed +
"General options:" + linefeed +
" -V|--version       print version information and exit." + linefeed +
" -d|--dbase dbx     associates the dbname with the database, unused." + linefeed +
"    --verbose       increases the verbosity level." + linefeed +
" -l|--list x|t|n    print x:VDLx, t:VDLt or just a table of n:names." + linefeed +
" -e|--error         if present, return failure for a combined empty result set." + linefeed +
" -E|--empty         if present and bundling, raise an error for any empty match." + linefeed +
" -o|--output fn     put the output into the file fn, default is stdout." + linefeed + 
linefeed +
"Group 1: Search for definitions" + linefeed +
" -t|--type tr|dv|all Search only for TR or DV, default is all (both)." + linefeed +
" -n|--namespace ns  Search for matches with namespace ns, default wildcard." + linefeed +
" -i|--name id       Search for matches with name id, default wildcard." + linefeed +
" -v|--ver vs        Search for matches with version vs, default wildcard." + linefeed +
" -D|--def dv[,..]   Search for matches of the complete FQDI (see manpage)." + linefeed +
" -L|--deflist lodvs read definition FQDIs from file lodvs, one per line." + linefeed +
linefeed +
"Group 2: Search for logical filenames" + linefeed +
" -t|--type i|o|io   Limit search to (i)n, (o)ut or (io) filenames." + linefeed +
" -f|--lfn lfn       Limit search to filename lfn, default wildcard." + linefeed +
" -F|--filelist lof  read the LFNs from file lof, one per line." + linefeed +
linefeed +
"The following exit codes are produced:" + linefeed +
" 0  :-)  Success" + linefeed +
" 1  :-|  Runtime error detected, please read the message." + linefeed +
" 2  :-?  empty result detected, and escalation (-e, -E) requested." + linefeed +
" 3  8-O  Fatal error merits a program abortion. Please carefully check your" + linefeed +
"         configuration files and setup before filing a bug report." + linefeed
			);
  }

  /**
   * Creates a set of options.
   */
  protected LongOpt[] generateValidOptions()
  {
    LongOpt[] lo = new LongOpt[21];

     lo[0] = new LongOpt( "dbase", LongOpt.REQUIRED_ARGUMENT, null, 'd' );
     lo[1] = new LongOpt( "version", LongOpt.NO_ARGUMENT, null, 'V' );
     lo[2] = new LongOpt( "help", LongOpt.NO_ARGUMENT, null, 'h' );
     lo[3] = new LongOpt( "verbose", LongOpt.NO_ARGUMENT, null, 1 );

     lo[4] = new LongOpt( "list", LongOpt.REQUIRED_ARGUMENT, null, 'l' );
     lo[5] = new LongOpt( "error", LongOpt.NO_ARGUMENT, null, 'e' );
     lo[6] = new LongOpt( "output", LongOpt.REQUIRED_ARGUMENT, null, 'o' );

     lo[7] = new LongOpt( "type", LongOpt.REQUIRED_ARGUMENT, null, 't' );
     lo[8] = new LongOpt( "namespace", LongOpt.REQUIRED_ARGUMENT, null, 'n' );
     lo[9] = new LongOpt( "ns", LongOpt.REQUIRED_ARGUMENT, null, 'n' );
    lo[10] = new LongOpt( "name", LongOpt.REQUIRED_ARGUMENT, null, 'i' );
    lo[11] = new LongOpt( "id", LongOpt.REQUIRED_ARGUMENT, null, 'i' );
    lo[12] = new LongOpt( "ver", LongOpt.REQUIRED_ARGUMENT, null, 'v' );
    lo[13] = new LongOpt( "vs", LongOpt.REQUIRED_ARGUMENT, null, 'v' );
    
    lo[14] = new LongOpt( "file", LongOpt.REQUIRED_ARGUMENT, null, 'f' );
    lo[15] = new LongOpt( "lfn", LongOpt.REQUIRED_ARGUMENT, null, 'f' );

    lo[16] = new LongOpt( "def", LongOpt.REQUIRED_ARGUMENT, null, 'D' );
    lo[17] = new LongOpt( "definition", LongOpt.REQUIRED_ARGUMENT, null, 'D' );
    lo[18] = new LongOpt( "deflist", LongOpt.REQUIRED_ARGUMENT, null, 'L' );
    lo[19] = new LongOpt( "filelist", LongOpt.REQUIRED_ARGUMENT, null, 'F' );
    lo[20] = new LongOpt( "empty", LongOpt.NO_ARGUMENT, null, 'E' );

    return lo;
  }

  /**
   * search the database for specific TR's or DV's
   */
  public static void main(String[] args) 
  {
    String groupError = "different option groups cannot be combined";
    boolean emptyFailure = false;
    boolean emptyBundle = false;
    boolean seenResults = false;
    int result = 0;

    try {
      SearchVDC me = new SearchVDC("searchvdc");

      // get the commandline options
      Getopt opts = new Getopt( me.m_application, args, 
				"D:EL:F:Vd:ef:hi:l:n:o:t:v:",
				me.generateValidOptions() );
      opts.setOpterr(false);
 
      int state = -1; // -1: undef, 0: dv(s), 1: lfn(s)
      String dbase = null;
      String ns = null;
      String id = null;
      String vs = null;
      String arg;

      String outfn = null;
      int classType = -1;
      int linkage = -1;
      int outputFormat = Search.FORMAT_FQDN;

      ArrayList definitions = new ArrayList();
      ArrayList filenames = new ArrayList();
      int option = 0;
      while ( (option = opts.getopt()) != -1 ) {
	switch ( option ) {
	case 1:
	  me.increaseVerbosity();
	  break;

	case 'V':
	  System.out.println( "$Id: SearchVDC.java 2079 2010-04-19 23:31:11Z vahi $" );
	  System.out.println( "VDS version " + Version.instance().toString() );
	  return;

	case 'D':
	  if ( state != -1 && state != 0 ) {
	    me.showUsage();
	    throw new RuntimeException( groupError );
	  } else {
	    arg = opts.getOptarg();
	    if ( arg != null ) {
	      state = 0;
	      StringTokenizer st = new StringTokenizer( arg, ",", false );
	      while ( st.hasMoreTokens() ) definitions.add( st.nextToken() );
	    }
	  }
	  break;
	  
	case 'E':
	  emptyBundle = true;
	  break;

	case 'F':
	  if ( state != -1 && state != 1 ) {
	    me.showUsage();
	    throw new RuntimeException( groupError );
	  } else {
	    arg = opts.getOptarg();
	    if ( arg != null ) {
	      state = 1;
	      int nr = me.readFile( arg, filenames );
	      me.m_logger.log( "app", 1, "read " + nr + " LFNs from " + arg );
	    }
	  }
	  break;

	case 'L':
	  if ( state != -1 && state != 0 ) {
	    me.showUsage();
	    throw new RuntimeException( groupError );
	  } else {
	    arg = opts.getOptarg();
	    if ( arg != null ) {
	      state = 0;
	      int nr = me.readFile( arg, definitions );
	      me.m_logger.log( "app", 1, "read " + nr + " DEF names from " + arg );
	    }
	  }
	  break;

	case 'd':
	  dbase = opts.getOptarg();
	  break;

	case 'e':
	  emptyFailure = true;
	  break;

	case 'f':
	  if ( state != -1 && state != 1 ) {
	    me.showUsage();
	    throw new RuntimeException( groupError );
	  } else {
	    arg = opts.getOptarg();
	    if ( arg != null ) {
	      state = 1;
	      StringTokenizer st = new StringTokenizer( arg, ",", false );
	      while ( st.hasMoreTokens() ) filenames.add( st.nextToken() );
	    }
	  }
	  break;

	case 'i':
	  if ( state != -1 && state != 0 ) {
	    me.showUsage();
	    throw new RuntimeException( groupError );
	  } else {
	    arg = opts.getOptarg();
	    if ( arg != null ) {
	      id = arg;
	      state = 0;
	    }
	  }
	  break;
	  
	case 'n':
	  if ( state != -1 && state != 0 ) {
	    me.showUsage();
	    throw new RuntimeException( groupError );
	  } else {
	    arg = opts.getOptarg();
	    if ( arg != null ) {
	      ns = arg;
	      state = 0;
	    }
	  }
	  break;

	case 'o':
	  arg = opts.getOptarg();
	  if ( arg != null ) outfn = arg;
	  break;
	  
	case 'v':
	  if ( state != -1 && state != 0 ) {
	    me.showUsage();
	    throw new RuntimeException( groupError );
	  } else {
	    arg = opts.getOptarg();
	    if ( arg != null ) {
	      vs = arg;
	      state = 0;
	    }
	  }
	  break;

	case 'l':
	  arg = opts.getOptarg().toLowerCase();
	  if ( arg != null ) {
	    switch ( arg.charAt(0) ) {
	    case 'x':
	      outputFormat = Search.FORMAT_VDLX;
	      break;
	    case 't':
	      outputFormat = Search.FORMAT_VDLT;
	      break;
	    default:
	      outputFormat = Search.FORMAT_FQDN;
	      break;
	    }
	  }
	  break;

	case 't':
	  arg = opts.getOptarg().toLowerCase();
	  if ( arg != null ) {
	    switch ( arg.charAt(0) ) {
	    case 'a':
	    case 'b':
	      if ( state != -1 && state != 0 ) {
		me.showUsage();
		throw new RuntimeException( groupError );
	      } else {
		state = 0;
		classType = -1; // both, all
	      }
	      break;
	    case 'd':
	      if ( state != -1 && state != 0 ) {
		me.showUsage();
		throw new RuntimeException( groupError );
	      } else {
		state = 0;
		classType = Definition.DERIVATION;
	      }
	      break;
	    case 't':
	      if ( state != -1 && state != 0 ) {
		me.showUsage();
		throw new RuntimeException( groupError );
	      } else {
		state = 0;
		classType = Definition.TRANSFORMATION;
	      }
	      break;
	    case 'i':
	      if ( state != -1 && state != 1 ) {
		me.showUsage();
		throw new RuntimeException( groupError );
	      } else {
		state = 1;
		linkage = arg.equals("io") ? LFN.INOUT : LFN.INPUT;
	      }
	      break;
	    case 'o':
	      if ( state != -1 && state != 1 ) {
		me.showUsage();
		throw new RuntimeException( groupError );
	      } else {
		state = 1;
		linkage = arg.equals("oi") ? LFN.INOUT : LFN.OUTPUT;
	      }
	      break;
	    default:
	      me.showUsage();
	      throw new RuntimeException( "Invalid value \"" + arg + "\" for option -t" );
	    }
	  }
	  break;

	case 'h':
	default:
	  me.showUsage();
	  return;
	}
      }

//      me.m_logger.log( "app", 3, filenames.size() + " LFNs and " + 
//		       definitions.size() + " DEFs, state=" + state );

      // Connect the database.
      String schemaName = ChimeraProperties.instance().getVDCSchemaName();

      Connect connect = new Connect();
      DatabaseSchema dbschema = connect.connectDatabase(schemaName);

      // Search the database.
      me.m_logger.log( "app", 2, "searching the database" );
      java.util.List resultList = new ArrayList();
      Search search = new Search(dbschema);

      java.util.List bundle = null;
      if ( state == 1 ) {
	// looking for LFNs
	if ( filenames.isEmpty() ) {
	  // wildcard LFN search
	  me.m_logger.log( "app", 3, "wildcard search requested" );
	  bundle = search.searchDefinition( null, linkage );
	  resultList.addAll( bundle );
	} else {
	  for ( Iterator i=filenames.iterator(); i.hasNext(); ) {
	    String lfn = (String) i.next();
	    bundle = search.searchDefinition( lfn, linkage );
	    
	    if ( bundle.isEmpty() && emptyBundle ) {
	      dbschema.close();
	      throw new FriendlyNudge( "no definitions found for \"" + lfn + "\"", 2 );
	    } else {
//	      me.m_logger.log( "app", 3, "found " + bundle.size() + 
//			       " matches for " + lfn );
	      resultList.addAll( bundle );
	    }
	  }
	}
      } else {
	// looking for definitions
	if ( definitions.isEmpty() ) {
	  // wildcard definition search
	  me.m_logger.log( "app", 3, "wildcard search requested" );
	  if ( dbschema instanceof Advanced )
	    bundle = search.searchDefinitionEx( ns, id, vs, classType );
	  else
	    bundle = search.searchDefinition( ns, id, vs, classType );
	  resultList.addAll( bundle );

	} else {
	  for ( Iterator i=definitions.iterator(); i.hasNext(); ) {
	    String def = (String) i.next();
	    // String[] d = Separator.split( def );
	    String[] d = Separator.splitFQDI( def );
	    if ( dbschema instanceof Advanced )
	      bundle = search.searchDefinitionEx( (d[0]==null ? ns : d[0]), 
						  (d[1]==null ? id : d[1]),
						  (d[2]==null ? vs : d[2]), 
						  classType );
	    else
	      bundle = search.searchDefinition( (d[0]==null ? ns : d[0]), 
						(d[1]==null ? id : d[1]),
						(d[2]==null ? vs : d[2]), 
						classType );

	    if ( bundle.isEmpty() && emptyBundle ) {
	      dbschema.close();
	      throw new FriendlyNudge( "no definitions found for \"" + def + "\"", 2 );
	    } else {
//	      me.m_logger.log( "app", 3, "found " + bundle.size() + 
//			       " matches for " + def );
	      resultList.addAll( bundle );
	    }
	  }
	}
      }
      me.m_logger.log( "app", 1, "found " + resultList.size() + " matches total" );

      if ( ! resultList.isEmpty() ) {
	seenResults = true;
	Writer writer;
	if ( outfn != null ) {
	  // save to file
	  me.m_logger.log("app", 1, "Saving to the file " + outfn );
	  writer = new BufferedWriter(new FileWriter(outfn));
	} else {
	  writer = new PrintWriter(System.out);
	}
	
	search.printDefinitionList( writer, resultList, outputFormat );
	writer.close();
      } else {
	me.m_logger.log( "app", 1, "no results" );
      }

      // done
      dbschema.close();
    } catch ( FriendlyNudge fn ) {
      System.err.println( fn.getMessage() );
      result = fn.getResult();
    } catch ( IOException ioe ) {
      System.err.println( "ERROR: " + ioe.getMessage() );
      result = 1;
    } catch ( RuntimeException rte ) {
      System.err.println( "ERROR: " + rte.getMessage() );
      result = 1;
    } catch ( Exception e ) {
      e.printStackTrace();
      System.err.println( "FATAL: " + e.getMessage() );
      result = 3;
    }

    // fail on empty result set, if requested
    if ( emptyFailure & ! seenResults ) result = 2;

    if ( result != 0 ) System.exit(result);
  }
}
